/*
  Copyright (c) 2010 Les Newell. All rights reserved

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "sockets.h"
#include "common.h"
#include "EEPROM/EEPROM.h"



//Case insensitive search - Arduino doesn't have stricmp
bool CompareStrings(const char * s1, const char * s2)
{
    while (1)
    {
        char c1 = *s1;
        s1++;
        char c2 = *s2;
        s2++;
        if (c1 != c2)
        {
            if (c1 >= 'a')
            {
                c1 -= 'a' - 'A';
            }
            if (c2 >= 'a')
            {
                c2 -= 'a' - 'A';
            }
            if (c1 != c2)
            {
                return(false);
            }
        }
        if (c1 == 0)
        {
            return(true);
        }
    }
}

#ifdef ARDUINO
bool CompareStringsP(const char * s1, const char * s2)
{
    while (1)
    {
        char c1 = *s1;
        s1++;
        char c2 = pgm_read_byte(s2);
        s2++;
        if (c1 != c2)
        {
            if (c1 >= 'a')
            {
                c1 -= 'a' - 'A';
            }
            if (c2 >= 'a')
            {
                c2 -= 'a' - 'A';
            }
            if (c1 != c2)
            {
                return(false);
            }
        }
        if (c1 == 0)
        {
            return(true);
        }
    }
}
#endif


SocketArray::~SocketArray()
{
}

void SocketArray::Add(const char * name, Socket * socket)
{
    SOCKETRECORD data;
    data.name = name;
    data.socket = socket;
    GrowArray<SOCKETRECORD>::Add(data);
}

Socket * SocketArray::Get(const char * name, const char * type)
{
    SOCKETRECORD* dat = NULL;

    for (int ct=0; ct< GetCount(); ct++)
    {
        SOCKETRECORD* data = &operator[](ct);
#ifdef ARDUINO
        if (CompareStringsP(name, data->name))
#else
        if (CompareStrings(name, data->name))
#endif
        {
            dat = data;
            break;
        }
    }
    if(!dat)
    {
        return(NULL);
    }
    if (!type || (type && CompareStrings(type, dat->socket->GetType())))
    {
        return(dat->socket);
    }
    Error("Types do not match. Plug is ");
    Error(type);
    Error(", Socket is ");
    Errorln(dat->socket->GetType());
    return(NULL);
}

bool SocketArray::Replace(Socket* oldSocket, Socket * newSocket)
{
    for (int ct=0; ct< GetCount(); ct++)
    {
        SOCKETRECORD* data = &operator[](ct);
        if(data->socket == oldSocket)
        {
            data->socket = newSocket;
            return(true);
        }
    }
    return(false);
}



Module::Module()
{
    g_moduleManager.Add(this);
    m_lastTime = 0;
    m_interval = 100;
    m_running = false;
    m_cpuTime = 0;
}

void Module::SetInterval(unsigned int time)
{
    m_interval = time;
    m_interval *= 1000; //internally we use microseconds
    m_lastTime = micros();
}

void Module::AddSocketArray(const char * names[], Socket8 sockets[], int n)
{
    for(int ct=0; ct< n; ct++)
    {
        m_sockets.Add(names[ct],&sockets[ct]);
    }
}

void Module::AddSocketArray(const char * names[], Socket16 sockets[], int n)
{
    for(int ct=0; ct< n; ct++)
    {
        m_sockets.Add(names[ct],&sockets[ct]);
    }
}

void Module::AddSocketArray(const char * names[], SocketF sockets[], int n)
{
    for(int ct=0; ct< n; ct++)
    {
        m_sockets.Add(names[ct],&sockets[ct]);
    }
}


void Module::ConnectPlugArray(const char * names[], PlugF plugs[], int n)
{
    for(int ct=0; ct< n; ct++)
    {
        plugs[ct].Connect(names[ct]);
    }
}



bool Module::Poll(const long& time)
{
    if(m_running)
    {
        return(false);
    }
    m_running = true;
    long elapsed = time - m_lastTime;
    if (elapsed < m_interval)
    {
        m_running = false;
        return(false);
    }
//    m_lastTime = time;
    m_lastTime += m_interval;
    Loop(elapsed);
    m_running = false;
    return(true);
}



ModuleManager::ModuleManager()
{
    m_modules.Grow(MAX_MODULES);
    m_threadCount = 0;
    m_overloadCount = 0;
    m_maxThreads = 0;
    memset(m_threadStack,-1,MAX_THREADS + 1);
    m_started = false;
//    m_threadStack[0] = -2;
}

void ModuleManager::Init()
{
    m_started = true;
    for (int ct=0; ct< m_modules.GetCount(); ct++)
    {
        m_modules[ct]->Init();
    }
    for (int ct=0; ct< m_modules.GetCount(); ct++)
    {
        Module * mod = m_modules[ct];
        SocketArray& sockets = mod->GetSockets();
        for (int var = 0; var < sockets.GetCount(); var++)
        {
            bool found = false;
#ifdef ARDUINO
            char varName[50];
            strlcpy_P(varName,sockets[var].name,50);
#else
            const char * varName = sockets[var].name;
#endif
            for (int ct2=0; ct2< GetCount(); ct2++)
            {
                if (ct == ct2)
                {
                    continue;
                }
                if (m_modules[ct2]->GetSocket(varName))
                {
                    if (!found)
                    {
                        Error("Variable conflict: ");
                        Error(varName);
                        Error(" provided by");
                        Error(mod->GetName());
                        found = true;
                    }
                    Error(", ");
                    Error(m_modules[ct2]->GetName());
                }
                if (found)
                {
                    Errorln("");
                }
            }
        }
    }

    bool found = false;
    do //bubble sort priorities
    {
        found = false;
        for(int ct=0; ct< m_modules.GetCount()-1; ct++)
        {
            if(m_modules[ct]->GetPriority() < m_modules[ct+ 1]->GetPriority())
            {
                m_modules.Swap(ct, ct+1);
                found = true;
            }
        }
    }while(found);
    ReadParameters();
    Reference();
}

void ModuleManager::Reference()
{
    for (int ct = m_modules.GetCount() -1; ct >=0 ; ct--)
    {
        Module * mod = m_modules[ct];
        mod->Reference();
    }
}

/*
void ModuleManager::Loop(const long& time)
{
    for (int ct=0; ct< GetCount(); ct++)
    {
        m_modules[ct]->Poll(time);
    }
}*/

signed char ModuleManager::m_threadStack[MAX_THREADS + 1];
GrowArray<Module *> ModuleManager::m_modules;
volatile int ModuleManager::m_threadCount = 0;
int ModuleManager::m_maxThreads = 0;
int ModuleManager::m_overloadCount = 0;
int ModuleManager::m_heartbeat = 0;


#define INFINITE_PRIORITY 127

void ModuleManager::InterruptHandler()
{
    m_heartbeat++;
    if(m_threadCount >= MAX_THREADS)
    {
        m_overloadCount ++;
        return;
    }
    m_threadCount++;

    signed char * stackPtr = &m_threadStack[m_threadCount];

    *stackPtr = INFINITE_PRIORITY;
    if(m_threadCount > m_maxThreads)
    {
        m_maxThreads = m_threadCount;
    }
    long time = micros();
    bool found;
    signed char highest = m_threadStack[m_threadCount -1];
    byte numModules = m_modules.GetCount();
    do
    {
        found = false;
        for(byte ct=0; ct< numModules; ct++)
        {
            Module * mod = m_modules.m_data[ct];
            int8_t pri = mod->GetPriority();
            if(mod->IsRunning() || pri <= highest)
            {
                break;
            }
            *stackPtr = pri;
            if(mod->Poll(time))
            {
                *stackPtr = INFINITE_PRIORITY;
                long oldTime = time;
                time = micros();
                mod->CheckTime(time - oldTime);
                found = true;
                break;
            }
        }
    }
    while(found);
    m_threadCount--;
}


Socket * ModuleManager::GetSocket(const char * name, const char * type, int * moduleNumber)
{

    if(strlen(name) <=4 && !type) //a socket ID
    {
        uint16_t num = 0;
        const char * ptr = name;
        while(*ptr !=0)
        {
            char c = *ptr++;
            uint8_t nibl;
            if(c >= '0' && c <= '9')
            {
                nibl = c - '0';
            }else if(c >= 'A' && c <= 'F')
            {
                nibl = (c - 'A') + 10;
            }else if(c >= 'a' && c <= 'f')
            {
                nibl = (c - 'a') + 10;
            }else
            {
                Errorln("Incorrect name format");
                return(NULL);
            }
            num = num << 4;
            num += nibl;
        }
        uint8_t module = (num >> 8) & 0xff;
        uint8_t socket = num & 0xff;
        if(module >= m_modules.GetCount())
        {
            Errorln("Incorrect module ID");
        }
        Module * modPtr = m_modules[module];
        if(socket >= modPtr->GetSockets().GetCount())
        {
            Errorln("Incorrect socket ID");
        }
        if(moduleNumber)
        {
            *moduleNumber = module;
        }
        return(modPtr->GetSockets().operator[](socket).socket);
    }


#ifdef ARDUINO
    char buf[50];
#endif


    for (int ct=0; ct< m_modules.GetCount(); ct++)
    {
        Module * module = m_modules[ct];
#ifdef ARDUINO
        Socket * val;
        if(type)
        {
            strlcpy_P(buf,name,50);
            val = module->GetSocket(buf, type);
        }else
        {
            val = module->GetSocket(name, type);
        }
#else
        Socket * val = module->GetSocket(name, type);
#endif
        if (val)
        {
            if(moduleNumber)
            {
                *moduleNumber = ct;
            }
            return(val);
        }
    }
    Error("Socket not found: ");
#ifdef ARDUINO
    if(type)
    {
        Errorln(buf);
    }else
    {
        Errorln(name);
    }
#else
    Errorln(name);
#endif
    return(NULL);
}

bool ModuleManager::ReplaceSocket(Socket* oldSocket, Socket * newSocket)
{
    if(m_started)
    {
        Errorln("ReplaceSocket must only be used in your constructor");
    }
    for (int ct=0; ct< m_modules.GetCount(); ct++)
    {
        Module * module = m_modules[ct];
        if(module->ReplaceSocket(oldSocket,newSocket))
        {
            return(true);
        }
    }
    return(false);
}

bool ModuleManager::ParseParameter(char * name, char * string)
{
    int modId;
    Socket * val = GetSocket(name,NULL,&modId);
    if(!val)
    {
        Error("Socket not found: ");
        return(false);
    }
    if (!val->IsParameter())
    {
        Errorln("Not a parameter");
        return(false);
    }

    val->Parse(string);
    m_modules[modId]->ParamChanged(val);
    return(true);
}

Module * ModuleManager::GetModule(const char * name)
{
    for (int ct=0; ct< m_modules.GetCount(); ct++)
    {
        Module * module = m_modules[ct];
        if (CompareStrings(module->GetName(),name))
        {
            return(module);
        }
    }
    return(NULL);
}


bool ModuleManager::WriteParameters()
{
    ReadWriteParameters(modeWRITE);
    CreateChecksum();
    byte * ptr = (byte *)(void *)&m_checksum;
    for (byte ct=0; ct< sizeof(m_checksum); ct++)
    {

        cli();
        EEPROM.write(ct,*ptr);
        sei();
        ptr++;
    }
}

bool ModuleManager::ReadParameters()
{
    byte * ptr = (byte *)(void *)&m_checksum;
    for (byte ct=0; ct< sizeof(m_checksum); ct++)
    {
        *ptr = EEPROM.read(ct);
        ptr++;
    }
    int memSum = m_checksum;
    CreateChecksum ();
    if (memSum != m_checksum)
    {
        Errorln("EEPROM Checksum failed - using defaults");
        return(false);
    }
    return(ReadWriteParameters(modeREAD));
}

#define MAX_EEPROM 512
/*this odd way of generating the checksum is so that hopefully the checksum gets
invalidated if you change the config*/
void ModuleManager::CreateChecksum()
{
    m_checksum = 0;
    ReadWriteParameters(modeCHECKSUM);
    m_checksum ^= 0xffff;
}


bool ModuleManager::ReadWriteParameters(const READMODE mode)
{
    int addr = sizeof(m_checksum);
    for (int ct=0; ct< m_modules.GetCount(); ct++)
    {
        Module * module = m_modules[ct];
        SocketArray& sockets = module->GetSockets();
        for (int v=0; v < sockets.GetCount(); v++)
        {
            Socket& val = *sockets[v].socket;
            if (val.IsParameter())
            {
                int siz = 0;
                byte * ptr = (byte *)val.GetPayload(siz);
                if (ptr)
                {
                    if (addr + siz >= MAX_EEPROM)
                    {
                        Errorln("Too many parmeters to fit in EEPROM");
                        return(false);
                    }
                    switch (mode)
                    {
                    case modeWRITE:
                        for (; siz > 0; siz --)
                        {
                            EEPROM.write(addr,*ptr);
                            addr++;
                            ptr++;
                        }
                        break;

                    case modeREAD:
                        for (; siz > 0; siz --)
                        {
                            *ptr = EEPROM.read(addr);
                            addr++;
                            ptr++;
                        }
                        module->ParamChanged(&val);
                        break;

                    case modeCHECKSUM:
                        for (; siz > 0; siz --)
                        {
                            m_checksum += EEPROM.read(addr);
                            addr++;
                            ptr++;
                        }
                        break;

                    }
                }
            }
        }
    }
    return(true);
}

#include "version.h"

void ModuleManager::PrintVersion(Print& print,const bool full)
{
    print.print("V");
    print.print(AutoVersion::MAJOR);
    print.print(".");
    print.print(AutoVersion::MINOR);
    print.print(" Build ");
    print.print(AutoVersion::BUILD);
    if(!full)
    {
        return;
    }
    print.print(" ");
    print.print(AutoVersion::DATE);
    print.print(":");
    print.print(AutoVersion::MONTH);
    print.print(":");
    print.print(AutoVersion::YEAR);


}

ModuleManager g_moduleManager;


#ifdef ARDUINO  //bodgery to put variable names in progmem


#undef PROGMEM
#define PROGMEM __attribute__(( section(".progmem.data") ))

#undef PSTR
#define PSTR(s) (__extension__({static prog_char __c[] PROGMEM = (s);&__c[0];}))

#define AddSocket(a,b) AddSocket(PSTR(a),b)
#define Connect(a) Connect(PSTR(a))

#define PROGMEM_ARRAY(name,n,s) static const prog_char name##n[] = s


void PrintProgString(Print * port, const char * string)
{
    while(1)
    {
        char c = pgm_read_byte(string++);
        if(c == 0)
        {
            return;
        }
        port->print(c);
    }
}


#endif

#include "config.h"


